﻿/* Copyright (C) 2008 - 2011 by John Cronin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

using Kernel;
using System.Collections.Generic;
using System.Text;

namespace tysos
{
    public class Program
    {
        internal static Environment env;
        internal static SymbolTable stab;
        internal static Cpu cur_cpu_data;

        internal static Arch arch;

        [ABI.ExtraArgument(0, 0x18)]
        static void Main()
        {
            // Disable profiling until we have enabled the arch.DebugOutput port
            do_profile = false;

            // Get the multiboot header
            Multiboot.Header mboot = ABI.CastOperations.ReinterpretAsMboot(ABI.CastOperations.GetArg0U8());

            // Choose the x86_64 architecture and set up a temporary heap
            MemoryAllocator.DefineHeap(mboot.heap_start + tysos.x86_64.Arch.GetRecommendedChunkLength(), mboot.heap_end);
            arch = new tysos.x86_64.Arch();
            arch.Init(new System.UIntPtr(mboot.heap_start), new System.UIntPtr(tysos.x86_64.Arch.GetRecommendedChunkLength()), mboot);

            // Say hi
            Formatter.WriteLine("Tysos v0.2.0", arch.BootInfoOutput);
            Formatter.WriteLine("Tysos v0.2.0", arch.DebugOutput);
            
            /* Map in the ELF image of the kernel, so we can load its symbols */
            ulong tysos_vaddr = map_in(mboot.tysos_paddr, mboot.tysos_size, "tysos binary");

            /* Find and map-in the test process */
            Multiboot.Module test_process_mod = find_module(mboot.modules, "TestProcess");
            ulong test_process_vaddr = map_in(test_process_mod);

            /* Set up a default environment */
            env = new Environment();
            env.env_vars.Add("OS", "tysos");
            env.env_vars.Add("OSVER", "v0.2.0");
            env.env_vars.Add("NUMBER_OF_PROCESSORS", "1");

            /* Load up the symbol table for tysos */
            stab = new SymbolTable();
            Formatter.Write("Loading kernel symbols... ", arch.BootInfoOutput);
            ElfReader.LoadSymbols(stab, tysos_vaddr, mboot.tysos_virtaddr);
            Formatter.WriteLine("done", arch.BootInfoOutput);

            /* Relocate our test program */
            ulong load_address = ElfReader.LoadModule(arch.VirtualRegions, arch.VirtMem, stab, test_process_vaddr, test_process_mod.base_addr, "TestProcess");

            /* Get the entry point address */
            ulong e_point = ElfReader.GetEntryPoint(test_process_vaddr, load_address);

            /* Create a process structure for the test program 1 */
            Process test_process = Process.Create("TestProcess", e_point, 0x1000, arch.VirtualRegions, stab);
            Formatter.WriteLine("Test process created", arch.DebugOutput);

            /* Load test program 2 */
            Multiboot.Module test_process_2_mod = find_module(mboot.modules, "TestProcess2");
            ulong test_process_2_mod_vaddr = map_in(test_process_2_mod);
            ulong load_address_2 = ElfReader.LoadModule(arch.VirtualRegions, arch.VirtMem, stab, test_process_2_mod_vaddr, test_process_2_mod.base_addr, "TestProcess2");
            ulong e_point_2 = ElfReader.GetEntryPoint(test_process_2_mod_vaddr, load_address_2);
            Process test_process_2 = Process.Create("TestProcess2", e_point_2, 0x1000, arch.VirtualRegions, stab);
            Formatter.WriteLine("Test process 2 created", arch.DebugOutput);

            /* Dump the current virtual region table */
            arch.VirtualRegions.Dump(arch.DebugOutput);

            /* Start the scheduler */
            cur_cpu_data.CurrentScheduler = new Scheduler();
            cur_cpu_data.CurrentScheduler.Reschedule(test_process.startup_thread);
            cur_cpu_data.CurrentScheduler.Reschedule(test_process_2.startup_thread);
            arch.SchedulerTimer.Callback = new Timer.TimerCallback(Scheduler.TimerProc);

            /* Start the processes */
            Formatter.WriteLine("Going multitasking...", arch.BootInfoOutput);
            arch.EnableMultitasking();
            //SchedulerFunctions.Yield();
            while (true) ;
        }

        private static Multiboot.Module find_module(Multiboot.Module[] modules, string name)
        {
            foreach (Multiboot.Module module in modules)
            {
                if (module.name == name)
                    return module;
            }
            throw new System.Exception("Module: " + name + " not found");
        }

        private static ulong map_in(Multiboot.Module mod)
        { return map_in(mod.base_addr, mod.length, mod.name); }

        internal static ulong map_in(ulong paddr, ulong size, string name)
        { return map_in(paddr, size, name, true, false, false); }

        internal static ulong map_in(ulong paddr, ulong size, string name, bool writeable, bool cache_disable, bool write_through)
        {
            ulong page_offset = paddr & 0xfff;
            ulong start = paddr - page_offset;
            ulong end = util.align(paddr + size, 0x1000);
            ulong length = end - start;
            ulong start_vaddr = arch.VirtualRegions.Alloc(length, 0x1000, name);
            ulong vaddr = start_vaddr + page_offset;
            for (ulong i = 0; i < length; i += 0x1000)
                arch.VirtMem.map_page(start_vaddr + i, start + i, writeable, cache_disable, write_through);
            return vaddr;
        }

        /* The following class is taken from Mono.  mono/corlib/System.Collections.Generic/EqualityComparer.cs
         * Authors: Ben Maurer (bmaurer@ximian.com), Copyright (C) 2004 Novell, Inc under the same license as this file
         * 
         * We need to use our own version of this as EqualityComparer<T> has a static constructor which calls
         * System.Activator, which we don't have yet */
        public class MyGenericEqualityComparer<T> : EqualityComparer<T> where T : System.IEquatable<T>
        {
            public override int GetHashCode(T obj)
            {
                return obj.GetHashCode();
            }

            public override bool Equals(T x, T y)
            {
                if (x == null)
                    return y == null;

                return x.Equals(y);
            }
        }

        public class MyComparer<T> : IComparer<T> where T : System.IComparable<T>
        {
            public int Compare(T x, T y)
            {
                return x.CompareTo(y);
            }
        }

        [ABI.MethodAlias("sthrow")]
        [ABI.AlwaysCompile]
        static void SimpleThrow(int exception_id)
        {
            if (exception_id == 1)
                Formatter.WriteLine("OverflowException", arch.BootInfoOutput);
            else if (exception_id == 2)
                Formatter.WriteLine("InvalidCastException", arch.BootInfoOutput);
            else if (exception_id == 3)
                Formatter.WriteLine("NullReferenceException", arch.BootInfoOutput);
            else if (exception_id == 4)
                Formatter.WriteLine("MissingMethodException", arch.BootInfoOutput);
            else if (exception_id == 5)
                Formatter.WriteLine("IndexOutOfRangeException", arch.BootInfoOutput);
            else
            {
                Formatter.Write("Unknown exception:", arch.BootInfoOutput);
                Formatter.Write((ulong)exception_id, "d", arch.BootInfoOutput);
            }

            /* Unwind the stack */
            PageFault.unwinding = true;
            Unwind u = new Unwind();
            Formatter.WriteLine("Stack trace: ", Program.arch.DebugOutput);

            while (true)
                PageFault.DumpIP(u.UnwindOne());


            ABI.OtherOperations.Halt();
        }

        [ABI.MethodAlias("throw")]
        [ABI.AlwaysCompile]
        static void Throw(System.Exception exception)
        {
            Formatter.WriteLine("Exception thrown!", arch.BootInfoOutput);
            Formatter.WriteLine(exception.ToString(), arch.BootInfoOutput);

            ABI.OtherOperations.Halt();
        }

        static int indent = 0;
        static bool do_profile = false;

        [ABI.MethodAlias("__profile")]
        [ABI.AlwaysCompile]
        [ABI.Profile(false)]
        static void Profile(string meth_name)
        {
            indent++;

            if (do_profile)
            {
                for (int i = 0; i < indent; i++)
                    Program.arch.DebugOutput.Write(' ');
                Program.arch.DebugOutput.Write("Enter: ");
                Program.arch.DebugOutput.Write(meth_name);
                Program.arch.DebugOutput.Write('\n');
            }
        }

        [ABI.MethodAlias("__endprofile")]
        [ABI.AlwaysCompile]
        [ABI.Profile(false)]
        static void EndProfile(string meth_name)
        {
            if (do_profile)
            {
                for (int i = 0; i < indent; i++)
                    Program.arch.DebugOutput.Write(' ');
                Program.arch.DebugOutput.Write("Leave: ");
                Program.arch.DebugOutput.Write(meth_name);
                Program.arch.DebugOutput.Write('\n');
            }

            indent--;
        }

        [ABI.MethodAlias("__mbstrlen")]
        [ABI.AlwaysCompile]
        unsafe internal static int MbStrLen(sbyte* value)
        {
            /* For ASCII strings only!  This doesn't properly handle UTF-8 yet */
            int length = 0;

            while (*value != 0x00)
            {
                length++;
                value++;
            }

            return length;
        }

        [ABI.MethodAlias("__mbstowcs")]
        [ABI.AlwaysCompile]
        unsafe static void MbsToWcs(char* dest, sbyte* src, int length)
        {
            for (int i = 0; i < length; i++)
            {
                /* For ASCII strings only!  This doesn't properly handle UTF-8 yet */
                *dest = (char)*src;

                dest++;
                src++;
            }
        }

        static int next_obj_id = 0x1000;
        [ABI.MethodAlias("__get_new_obj_id")]
        [ABI.AlwaysCompile]
        static int GetNewObjId()
        {
            // TODO: implement locking on this
            // The unchecked keyword allows the value to wrap round on overflow instead of throwing an exception
            unchecked
            {
                return next_obj_id++;
            }
        }

        [ABI.MethodAlias("__undefined_func")]
        [ABI.AlwaysCompile]
        static void UndefinedFunc(ulong addr)
        {
            Formatter.Write("Undefined function called from: 0x", arch.BootInfoOutput);
            Formatter.Write(addr, "X", arch.BootInfoOutput);
            Formatter.WriteLine(arch.BootInfoOutput);
        }

        [ABI.MethodAlias("__display_halt")]
        [ABI.AlwaysCompile]
        static void DisplayHalt()
        {
            Formatter.WriteLine("System halted", arch.BootInfoOutput);
        }

        [ABI.MethodAlias("__exit")]
        [ABI.AlwaysCompile]
        static void Exit()
        {
            Formatter.WriteLine("Thread exited normally", arch.BootInfoOutput);
            ABI.OtherOperations.Halt();
        }

        [ABI.MethodAlias("__get_cur_thread_id")]
        [ABI.AlwaysCompile]
        static int GetCurThreadId()
        {
            if (cur_cpu_data == null)
                return 1;
            else if (cur_cpu_data.CurrentThread == null)
                return 1;
            else
                return cur_cpu_data.CurrentThread.thread_id;
        }

        [ABI.MethodAlias("_m8_mscorlib_N6_System_T11_Environment_S6_12843C_M30_internalGetEnvironmentVariable_S8_00010E0E")]
        [ABI.AlwaysCompile]
        static string GetEnvVar(string var_name)
        {
            if (env.env_vars.ContainsKey(var_name))
                return env.env_vars[var_name];
            return "";
        }

        [ABI.MethodAlias("_m8_mscorlib_N18_System#2EThreading_T6_Thread_S6_129AAC_M23_GetCachedCurrentCulture_S10_200012880C")]
        [ABI.AlwaysCompile]
        static System.Globalization.CultureInfo Thread_GetCachedCurrentCulture(System.Threading.Thread thread)
        {
            /* For now, we just return the invariant culture
             * This is required to get NumberFormatter to work to convert numbers to strings
             */

            return System.Globalization.CultureInfo.InvariantCulture;
        }

        static double TestDiv_r8()
        {
            double val_1 = 60.0;
            double val_2 = 12.0;

            double val_3 = val_1 / val_2;

            double val_4 = val_3 / System.Convert.ToDouble(5);

            return val_4;
        }
    }
}
